package minimips.computer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import minimips.isa.Instruction;
import minimips.util.BitUnits;

public class Computer {

	//instruction fetch registers
	private Register if_id_ir,
					if_id_npc;
	
	//instruction decode registers
	private Register id_ex_ir,
					id_ex_a,
					id_ex_b,
					id_ex_imm;
	
	//execution registers
	private Register ex_mem_ir,
					ex_mem_aluOutput,
					ex_mem_b,
					ex_mem_cond;
	
	//memory access registers
	private Register mem_wb_ir,
					mem_wb_aluOutput,
					mem_wb_lmd;
	
	private Register hi, lo;
	private ArrayList<Register> programmableRegisters;
	
	/**Keep track of registers being used
	 * 
	 */
	private List<Integer> registersInUse;
	private Map<Integer, Long> registerCache;
	
	private Memory memory;
	
	public static final int FIRST_CODE_SEGMENT_ADDRESS = 0x0;
	public static final int CODE_SEGMENT_OFFSET = 4;
	private int topCodeSegmentAddress = FIRST_CODE_SEGMENT_ADDRESS;
	private Map<Integer, Instruction> codeSegment;
	
	private boolean memoryInUse;
	
	private JumpCondition jumpChecker;
	
	public Computer(int numberOfProgrammableRegisters) {
		this.programmableRegisters = new ArrayList<Register>();
		for (int i = 0; i < numberOfProgrammableRegisters; i++) {
			this.programmableRegisters.add(new Register());
		}
		this.if_id_ir = new Register(0);
		this.if_id_npc = new Register(0);
		this.id_ex_ir = new Register(0);
		this.id_ex_a = new Register(0);
		this.id_ex_b = new Register(0);
		this.id_ex_imm = new Register(0);
		this.ex_mem_ir = new Register(0);
		this.ex_mem_aluOutput = new Register(0);
		this.ex_mem_b = new Register(0);
		this.ex_mem_cond = new Register(0);
		this.mem_wb_ir = new Register(0);
		this.mem_wb_aluOutput = new Register(0);
		this.mem_wb_lmd = new Register(0);
		this.memory = new Memory(0x1FFF, 0x2000);
		this.codeSegment = new Hashtable<Integer, Instruction>();
		
		this.hi = new Register(0);
		this.lo = new Register(0);
		
		this.registersInUse = new ArrayList<Integer>();
		this.memoryInUse = false;
		
		this.jumpChecker = new JumpCondition();
		this.registerCache = new HashMap<Integer, Long>();
	}
	
	public Computer() {
		this(31);
	}
	
	public void addRegisterBeingUsed(Integer... registerIndices) {
		this.registersInUse.addAll(Arrays.asList(registerIndices));
	}
	
	public void removeRegisterBeingUsed(Integer... registerIndices) {
		this.registersInUse.removeAll(Arrays.asList(registerIndices));
	}
	
	public boolean isRegisterIndexInUse(Integer registerIndex) {
		return this.registersInUse.contains(registerIndex);
	}
	
	public Register getIf_id_ir() {
		return if_id_ir;
	}
	
	public void addIf_Id_IrListener(LoadStoreListener<Register> listener) {
		this.if_id_ir.addRegisterChangeListener(listener);
	}


	public Register getIf_id_npc() {
		return if_id_npc;
	}
	
	public void addIf_Id_NpcListener(LoadStoreListener<Register> listener) {
		this.if_id_npc.addRegisterChangeListener(listener);
	}
	
	public Register getId_ex_ir() {
		return id_ex_ir;
	}
	
	public void addId_Ex_IrListener(LoadStoreListener<Register> listener) {
		this.id_ex_ir.addRegisterChangeListener(listener);
	}


	public Register getId_ex_a() {
		return id_ex_a;
	}

	public void addId_Ex_AListener(LoadStoreListener<Register> listener) {
		this.id_ex_a.addRegisterChangeListener(listener);
	}

	public Register getId_ex_b() {
		return id_ex_b;
	}
	
	public void addId_Ex_BListener(LoadStoreListener<Register> listener) {
		this.id_ex_b.addRegisterChangeListener(listener);
	}

	public Register getId_ex_imm() {
		return id_ex_imm;
	}
	
	public void addId_Ex_ImmListener(LoadStoreListener<Register> listener) {
		this.id_ex_imm.addRegisterChangeListener(listener);
	}

	public Register getEx_mem_ir() {
		return ex_mem_ir;
	}

	public void addEx_Mem_IrListener(LoadStoreListener<Register> listener) {
		this.ex_mem_ir.addRegisterChangeListener(listener);
	}
	
	public Register getEx_mem_aluOutput() {
		return ex_mem_aluOutput;
	}
	
	public void addEx_Mem_AluOutputListener(LoadStoreListener<Register> listener) {
		this.ex_mem_aluOutput.addRegisterChangeListener(listener);
	}

	public Register getEx_mem_b() {
		return ex_mem_b;
	}

	public void addEx_Mem_BListener(LoadStoreListener<Register> listener) {
		this.ex_mem_b.addRegisterChangeListener(listener);
	}
	
	public Register getEx_mem_cond() {
		return ex_mem_cond;
	}
	
	public void addEx_Mem_CondListener(LoadStoreListener<Register> listener) {
		this.ex_mem_cond.addRegisterChangeListener(listener);
	}

	public Register getMem_wb_ir() {
		return mem_wb_ir;
	}

	public void addMem_Wb_IrListener(LoadStoreListener<Register> listener) {
		this.mem_wb_ir.addRegisterChangeListener(listener);
	}

	public Register getMem_wb_aluOutput() {
		return mem_wb_aluOutput;
	}

	public void addMem_Wb_AluOutputListener(LoadStoreListener<Register> listener) {
		this.mem_wb_aluOutput.addRegisterChangeListener(listener);
	}

	public Register getMem_wb_lmd() {
		return mem_wb_lmd;
	}

	public void addMem_Wb_LmdListener(LoadStoreListener<Register> listener) {
		this.mem_wb_lmd.addRegisterChangeListener(listener);
	}
	
	public Register getHi() {
		return hi;
	}

	public Register getLo() {
		return lo;
	}

	public int getMemorySize() {
		return memory.getMemorySize();
	}

	public int getStartingMemoryAddress() {
		return memory.getStartingMemoryAddress();
	}

	public int getLastMemoryAddress() {
		return memory.getLastAddress();
	}

	public int getFromMemoryAddress(Integer address)
			throws IllegalArgumentException {
		return memory.getFromMemoryAddress(address);
	}

	public long getManyFromMemoryAddress(Integer address, BitUnits numberOfBits)
			throws IllegalArgumentException {
		return memory.getManyFromMemoryAddress(address, numberOfBits);
	}

	public void setToMemoryAddress(Integer address, Integer value) {
		memory.setToMemoryAddress(address, value);
	}

	public void setToMemoryAddress(Integer startAddress, BitUnits numberOfBits,
			long value) {
		memory.setToMemoryAddress(startAddress, numberOfBits, value);
	}

	public void addMemoryLoadStoreListener(LoadStoreListener<MemoryEventObject> listener) {
		this.memory.addMemoryLoadStoreListener(listener);
	}
	
	public void addInstruction(Instruction instruction) {
		this.codeSegment.put(topCodeSegmentAddress, instruction);
		topCodeSegmentAddress += CODE_SEGMENT_OFFSET;
	}

	public Instruction getInstruction(int instructionAddress) {
		return this.codeSegment.get(instructionAddress);
	}
	
	public boolean isMemoryInUse() {
		return memoryInUse;
	}

	public void setMemoryInUse(boolean memoryInUse) {
		this.memoryInUse = memoryInUse;
	}

	public long getProgrammableRegisterValue(int index) {
		return this.programmableRegisters.get(index).getRegisterValue();
	}
	
	public void setProgrammableRegisterValue(int index, long value) {
		this.programmableRegisters.get(index).setRegisterValue(value);
	}
	
	public void setProgrammableRegisterValue(int index, Register register) {
		this.programmableRegisters.get(index).setRegisterValue(register);
	}
	
	public boolean willJump() {
		return jumpChecker.willJump();
	}
	
	public boolean willBranch() {
		return jumpChecker.willBranch();
	}

	public void enableJump(int absoluteAddress) {
		jumpChecker.enableJump(absoluteAddress);
	}

	public void enableBranch(int offset) {
		jumpChecker.enableBranch(offset);
	}

	public void resetJumpChecker() {
		jumpChecker.reset();
	}
	
	public int getJumpValue() {
		return this.jumpChecker.getJumpValue();
	}
	
	public void putValueInCache(Integer registerIndex, Long value) {
		this.registerCache.put(registerIndex, value);
	}
	
	public void removeValueInCache(Integer registerIndex) {
		this.registerCache.remove(registerIndex);
	}
	
	public long getValueInCache(Integer registerIndex) {
		return this.registerCache.get(registerIndex);
	}
		
}
